﻿using System.Windows.Media.Imaging;
using Gamelight.Data;

namespace Gamelight.Graphics
{
	public sealed class Image
	{
		private WriteableBitmap bmp;
		private int width;
		private int height;
		private int[] pixels;

		public int Width { get { return this.width; } }
		public int Height { get { return this.height; } }

		private byte blitAlpha = 255;
		public byte BlitAlpha
		{
			get { return this.blitAlpha; }
			set { this.blitAlpha = value; }
		}

		private int xOffset = 0;
		private int yOffset = 0;

		internal int[] Pixels
		{
			get
			{
				return this.pixels;
			}
		}

		public Image(int width, int height)
			: this(width, height, 0, 0)
		{ }

		public Image(int width, int height, int xOffset, int yOffset)
		{
			this.pixels = new int[width * height];

			this.width = width;
			this.height = height;

			this.xOffset = xOffset;
			this.yOffset = yOffset;
		}

		internal Image(WriteableBitmap writeableBitmap)
		{
			this.bmp = writeableBitmap;
			this.pixels = this.bmp.Pixels;
			this.width = writeableBitmap.PixelWidth;
			this.height = writeableBitmap.PixelHeight;
		}

		public Image(string imagePathInDefaultAssembly) :
			this(Resources.GetImageCopy(null, imagePathInDefaultAssembly))
		{
		}

		public Image(string assemblyName, string pathWithinAssembly) :
			this(Resources.GetImageCopy(assemblyName, pathWithinAssembly))
		{
		}

		public Image(Image surfaceToCopy)
		{
			this.pixels = new int[surfaceToCopy.width * surfaceToCopy.height];
			surfaceToCopy.pixels.CopyTo(this.pixels, 0);

			this.width = surfaceToCopy.width;
			this.height = surfaceToCopy.height;
			this.xOffset = surfaceToCopy.xOffset;
			this.yOffset = surfaceToCopy.yOffset;
		}

		internal WriteableBitmap Bmp
		{
			get { return this.bmp; }
			set { this.bmp = value; }
		}

		public void Fill(Color color)
		{
			byte r = color.Red;
			byte g = color.Green;
			byte b = color.Blue;
			byte a = color.Alpha;

			int[] pixels = this.Pixels;
			int colorValue = (a << 24) + (r << 16) + (g << 8) + b;
			for (int i = 0; i < pixels.Length; ++i)
			{
				pixels[i] = colorValue;
			}
		}

		internal int XOffset
		{
			get { return this.xOffset; }
			set { this.xOffset = value; }
		}

		internal int YOffset
		{
			get { return this.yOffset; }
			set { this.yOffset = value; }
		}

		public void Blit(Image source, int x, int y)
		{
			x += source.xOffset;
			y += source.yOffset;

			int shift;

			// Width cropping
			int targetXstart = x;
			int targetXend = targetXstart + source.width - 1;

			int sourceXstart = 0;
			int sourceXend = source.width - 1;

			if (targetXstart < 0)
			{
				sourceXstart -= targetXstart;
				targetXstart = 0;
			}

			int maxX = this.width - 1;

			if (targetXend > maxX)
			{
				shift = maxX - targetXend;
				sourceXend -= shift;
				targetXend = maxX;
			}

			//Height cropping
			int targetYstart = y;
			int targetYend = targetYstart + source.height - 1;

			int sourceYstart = 0;
			int sourceYend = source.height - 1;

			if (targetYstart < 0)
			{
				sourceYstart -= targetYstart;
				targetYstart = 0;
			}

			int maxY = this.height - 1;

			if (targetYend > maxY)
			{
				shift = maxY - targetYend;
				sourceYend -= shift;
				targetYend = maxY;
			}

			int sourceX;
			int sourceY;
			int targetX;
			int targetY;

			int newR;
			int newG;
			int newB;
			int newA;

			int oldR;
			int oldG;
			int oldB;
			int oldA;

			int newValue;
			int oldValue;

			int[] sourcePixels = source.Pixels;
			int[] targetPixels = this.Pixels;

			int sourceWidth = source.width;
			int targetWidth = this.width;

			int index;
			int finalAlpha;
			int oldWeight;

			int blitAlpha = source.blitAlpha;

			for (targetY = targetYstart, sourceY = sourceYstart;
				targetY <= targetYend;
				targetY++, sourceY++)
			{
				for (targetX = targetXstart, sourceX = sourceXstart;
					targetX <= targetXend;
					targetX++, sourceX++)
				{
					
					newValue = sourcePixels[sourceWidth * sourceY + sourceX];
					if (blitAlpha == 255)
					{
						newA = ((newValue >> 24) & 255);
					}
					else
					{
						newA = ((newValue >> 24) & 255) * blitAlpha / 255;
					}

					if (newA == 255)
					{
						targetPixels[targetWidth * targetY + targetX] = newValue;
					}
					else if (newA > 0)
					{
						index = targetWidth * targetY + targetX;
						newR = (newValue >> 16) & 255;
						newG = (newValue >> 8) & 255;
						newB = newValue & 255;

						oldValue = targetPixels[index];
						oldA = (oldValue >> 24) & 255;
						oldR = (oldValue >> 16) & 255;
						oldG = (oldValue >> 8) & 255;
						oldB = oldValue & 255;

						finalAlpha = 255 - (255 - oldA) * (255 - newA) / 255;
						oldWeight = oldA * (255 - newA) / 255;
						
						newR = (oldR * oldWeight + newR * newA) / (oldWeight + newA);
						newG = (oldG * oldWeight + newG * newA) / (oldWeight + newA);
						newB = (oldB * oldWeight + newB * newA) / (oldWeight + newA);
						
						targetPixels[index] = (finalAlpha << 24) + (newR << 16) + (newG << 8) + newB;
					}
				}
			}
		}

		public Color GetColor(int x, int y)
		{
			if (x < 0 ||
				x >= this.width ||
				y < 0 ||
				y >= this.height)
			{
				throw new System.IndexOutOfRangeException();
			}

			return new Color(this.Pixels[x + y * this.width]);
		}

		public void SetColor(int x, int y, Color color)
		{
			if (x < 0 ||
				x >= this.width ||
				y < 0 ||
				y >= this.height)
			{
				throw new System.IndexOutOfRangeException();
			}

			this.Pixels[x + y * this.width] = color.IntValue;
		}

		public void ReplaceColor(Color oldColor, Color newColor)
		{
			int oldValue = oldColor.IntValue;
			int newValue = newColor.IntValue;
			int[] pixels = this.Pixels;
			int length = pixels.Length;

			for (int i = 0; i < length; ++i)
			{
				if (pixels[i] == oldValue)
				{
					pixels[i] = newValue;
				}
			}
		}
	}
}
